/*
 * \brief  Child implementation of Init
 * \author Norman Feske
 * \date   2006-09-14
 */

/*
 * Copyright (C) 2006-2009 Norman Feske
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#ifndef _INCLUDE__INIT__CHILD_H_
#define _INCLUDE__INIT__CHILD_H_

#include <base/env.h>
#include <base/child.h>
#include <base/service.h>
#include <cap_session/cap_session.h>
#include <rom_session/server.h>
#include <rom_session/capability.h>
#include <base/snprintf.h>

namespace Init {

	class Child : public Genode::List<Child>::Element, public Genode::Child
	{
		private:

			enum { NAME_LEN = 64 };
			char _name[NAME_LEN];

			class Config_rom_session_component : public Genode::Rom_session_server
			{
				private:

					Genode::Dataspace_capability _config_ds;

				public:

					/**
					 * Constructor
					 */
					Config_rom_session_component(Genode::Dataspace_capability config_ds)
					: _config_ds(config_ds) { }


					/***************************
					 ** ROM session interface **
					 ***************************/

					Genode::Rom_dataspace_capability dataspace() {
						return Genode::Rom_dataspace_capability(_config_ds); }
			};

			Config_rom_session_component   _config_rom_session;
			Genode::Rom_session_capability _config_rom_session_cap;

		protected:

			Genode::Remote_service_pool *_remote_services;

			char _argbuf[256];

			static Genode::size_t _ram_quota(const char *args) {
				return Genode::Arg_string::find_arg(args, "ram_quota").long_value(0); }

			void _prepend_label(const char *args)
			{
				using namespace Genode;

				char label_buf[64];
				Arg_string::find_arg(args, "label").string(label_buf, sizeof(label_buf), "");

				strncpy(_argbuf, args, sizeof(_argbuf));
				char value_buf[64];
				snprintf(value_buf, sizeof(value_buf),
				         "\"%s%s%s\"",
				         name(),
				         strcmp(label_buf, "") == 0 ? "" : " -> ",
				         label_buf);

				Arg_string::set_arg(_argbuf, sizeof(_argbuf), "label", value_buf);
			}

			/**
			 * Decide if a session request should be delegated to our parent
			 *
			 * We should do this only if we are confident that such a service
			 * exists at our parent. If the a service is not known to our
			 * parent, the parent may enqueue us into his wait-for-service
			 * queue and block the request.
			 */
			virtual bool _delegate_to_parent(const char *service_name, const char *args)
			{
				using namespace Genode;

				/* if service is provided by one of our children, use it */
				if (_remote_services->find(service_name))
					return false;

				/*
				 * Whitelist of services that our parent implements for sure.
				 * Connecting to one of these services won't block.
				 */
				if (!strcmp(service_name, "CAP")
				 || !strcmp(service_name, "RAM")
				 || !strcmp(service_name, "RM")
				 || !strcmp(service_name, "PD")
				 || !strcmp(service_name, "CPU")
				 || !strcmp(service_name, "IO_MEM")
				 || !strcmp(service_name, "IO_PORT")
				 || !strcmp(service_name, "IRQ")
				 || !strcmp(service_name, "ROM")
				 || !strcmp(service_name, "LOG"))
					return true;

				return false;
			}

		public:

			/**
			 * Constructor
			 *
			 * \param name             name of child (used for debugging)
			 * \param elf_ds           ELF binary of program to run
			 * \param ram_session      RAM session for child
			 * \param cpu_session      CPU session for child
			 * \param cap_session      capability session for child
			 * \param remote_services  service pool for child
			 * \param config_ds        capability for configuration dataspace
			 *
			 * See the description of 'Genode::Child' for the other
			 * arguments.
			 */
			Child(const char                     *name,
			      Genode::Dataspace_capability    elf_ds,
			      Genode::Ram_session_capability  ram_session,
			      Genode::Cpu_session_capability  cpu_session,
			      Genode::Cap_session            *cap_session,
			      Genode::Remote_service_pool    *remote_services,
			      Genode::Dataspace_capability    config_ds)
			:
				Genode::Child(name, elf_ds, ram_session, cpu_session, cap_session, 0),

				/* create overloaded ROM session for the child's configuration */
				_config_rom_session(config_ds),

				/* make config ROM session accessible via RPC */
				_config_rom_session_cap(parent_entrypoint()->manage(&_config_rom_session)),
				_remote_services(remote_services)
			{
				Genode::strncpy(_name, name, sizeof(_name));
			}


			/**
			 * Destructor
			 */
			~Child()
			{
				/* unregister services that had been provided by the child */
				Genode::Remote_service *rs;
				while ((rs = _remote_services->find_by_server(this)))
					_remote_services->remove(rs);

				parent_entrypoint()->dissolve(&_config_rom_session);
			}


			const char *name() { return _name; }


			/**********************
			 ** Parent interface **
			 **********************/

			Genode::Session_capability session(const char *service_name, const char *args)
			{
				using namespace Genode;

				/* check if request refers to an initial session */
				try {
					return Genode::Child::session(service_name, args);
				} catch (...) { }

				/*
				 * Check if a ROM session for the 'config' file is requested.
				 * In this case, we provide the ROM session by ourself and
				 * present the child's part of the configuration.
				 */
				if (!strcmp(service_name, "ROM")) {

					/*
					 * We only need to check if the filename equals 'config'.
					 * Hence, the low dimensioning of the 'filename' buffer.
					 */
					char filename[8];
					Arg_string::find_arg(args, "filename").string(filename, sizeof(filename), "");
					if (!strcmp(filename, "config"))
						return _config_rom_session_cap;
				}

				/*
				 * All other sessions require quota to be donated from the
				 * client to the server. First, we transfer the quota
				 * donation from the child's account to ourself.
				 */
				long ram_quota = _ram_quota(args);
				if (_ram_session_client.transfer_quota(env()->ram_session_cap(), ram_quota)) {
					PWRN("client has not enough quota for donation of %ld bytes to the server", ram_quota);
					throw Quota_exceeded();
				}

				_prepend_label(args);

				/*
				 * Try delegating requests to our parent.
				 * We need to be aware that our parent will store is session
				 * information by debiting our account.
				 */
				if (_delegate_to_parent(service_name, _argbuf)) {

					Session_capability session_cap;

					try {
						session_cap = env()->parent()->session(service_name, _argbuf);
					} catch (...) {

						/* revert quota transfer on error */
						env()->ram_session()->transfer_quota(_ram_session_cap, ram_quota);

						/* let exception fall through */
						throw;
					}

					try {
						_add_session(Session(session_cap, env()->parent(), ram_quota, 0, service_name));
					} catch (Ram_session::Quota_exceeded) {
						throw Quota_exceeded();
					}

					return session_cap;
				}

				/* search for a matching registered service */
				Remote_service *rs = _remote_services->wait_for_service(service_name, this);
				if (rs) {

					/* transfer session quota from ourself to the server */
					if (env()->ram_session()->transfer_quota(rs->server()->ram_session_cap(), ram_quota)) {
						PWRN("cannot make session quota donation to server");
						throw Quota_exceeded();
					}

					Session_capability session_cap;

					try {
						session_cap = rs->root()->session(_argbuf);
					} catch (Root::Quota_exceeded) {
						throw Quota_exceeded();
					} catch (Root::Invalid_args) {
						throw Service_denied();
					}

					try {
						_add_session(Session(session_cap, rs->root(), ram_quota, rs->server(), service_name));
					} catch (Ram_session::Quota_exceeded) {
						PWRN("Child's heap partition exceeded");
						throw Quota_exceeded();
					}

					return session_cap;
				}

				/* raise an error if no matching service was found */
				throw Service_denied();
			}

			int announce(const char *service_name, Genode::Root_capability service_root)
			{
				using namespace Genode;

				if (_remote_services->find(service_name)) {
					PWRN("%s: service %s is already registered", name(), service_name);
					return -1;
				}

				_remote_services->insert(new (heap()) Remote_service(service_name, service_root, this));
				Genode::printf("%s registered service %s\n", name(), service_name);
				return 0;
			}
	};
}

#endif /* _INCLUDE__INIT__CHILD_H_ */
